﻿

Public Class InnerZipMaker
    Implements IDisposable
    'Ctor :
    Public Sub New(ByVal zipnode As ZipNode)
        Control.CheckForIllegalCrossThreadCalls = False
        _ZipNode = zipnode
    End Sub
    'Dtor :
    Public Sub Dispose() Implements IDisposable.Dispose
        If _BackMaker IsNot Nothing AndAlso _BackMaker.IsBusy Then
            _BackMaker.CancelAsync()
        End If
        If _BackSaver IsNot Nothing AndAlso _BackSaver.IsBusy Then
            CancelZip()
        End If
        _ZipFile.Dispose()
    End Sub
    'Zip Node :
    Private _Filters As List(Of Filter)
    Private _ZipNode As ZipNode
    Public ReadOnly Property ZipNode As ZipNode
        Get
            Return _ZipNode
        End Get
    End Property
    'Zip Progress :
    Private _ZipTotalSize As Long
    Public ReadOnly Property ZipTotalSize As Long
        Get
            Return _ZipTotalSize
        End Get
    End Property
    Private _ZipWrittenSize As Long
    Public ReadOnly Property ZipWrittenSize As Long
        Get
            Return _ZipWrittenSize
        End Get
    End Property
    Private _ZipPercent As Double
    Public ReadOnly Property ZipPercent As Double
        Get
            Return _ZipPercent
        End Get
    End Property
    Public ReadOnly Property TempZipLocation As String
        Get
            Return _ZipLocation
        End Get
    End Property
    'Actions :
    Public Function CanMakeZip() As Boolean
        If _IsWorking = False Then
            If _ZipNode.InnerZipNodes.Count = 0 Then
                Return True
            Else
                Return False
            End If
        Else
            Return False
        End If
    End Function
    Public Sub MakeSaveZip(ByVal filters As List(Of Filter))
        ResetProgressFields()
        _Filters = filters
        _BackMaker = New BackgroundWorker With {.WorkerSupportsCancellation = True}
        _ZipFile = New Zip.ZipFile
        _ZipFile.ZipErrorAction = Zip.ZipErrorAction.InvokeErrorEvent
        _ZipNode.ZipSettings.ApplyToZip(_ZipFile)
        If _ZipNode.Type = ZipType.SplitZip Then
            _ZipNode.ZipSettings.Splitting.ApplyToZip(_ZipFile)
        End If
        _BackMaker.RunWorkerAsync()
    End Sub
    Public Sub CancelZip()
        If _CancelSaving = False Then
            _CancelSaving = True
        End If
    End Sub

    'Reset Progress Fields :
    Private Sub ResetProgressFields()
        _ZipTotalSize = 0L
        _ZipWrittenSize = 0L
        _ZipPercent = 0.0
        _LastEntryWrittenBytes = 0L
    End Sub
    'Updating Bytes Written :
    Private _LastEntryWrittenBytes As Long
    Private Sub UpdateBytes(ByVal entrywrittenbytes As Long, ByRef entrysize As Long)
        _ZipWrittenSize += entrywrittenbytes - _LastEntryWrittenBytes
        _ZipPercent = CDbl(_ZipWrittenSize / _ZipTotalSize)
        If entrywrittenbytes = entrysize Then
            _LastEntryWrittenBytes = 0
        Else
            _LastEntryWrittenBytes = entrywrittenbytes
        End If
    End Sub

    'Background Work :
    Private WithEvents _ZipFile As Zip.ZipFile
    Public ReadOnly Property ZipFile As Zip.ZipFile
        Get
            Return _ZipFile
        End Get
    End Property
    Private _IsWorking As Boolean = False
    Public ReadOnly Property IsWorking As Boolean
        Get
            Return _IsWorking
        End Get
    End Property

    Private WithEvents _BackMaker As BackgroundWorker
    Private WithEvents _BackSaver As BackgroundWorker

    'Background Making :
    Private Sub _BackMaker_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles _BackMaker.DoWork
        _IsWorking = True
        _ZipNode.Clean()
        Dim _EntriesFilesNodes As List(Of FileNode) = _ZipNode.InnerFilesNode
        _ZipTotalSize = _ZipNode.Size
        RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Loading", Nothing))
        For Each fn As FileNode In _EntriesFilesNodes
            AddHandler _ZipFile.AddProgress, AddressOf _Zip_AddProgress
            Dim ze As Zip.ZipEntry = _ZipFile.AddFile(fn.SourcePath, fn.ZipPath)
            fn.EntrySettings.ApplyToZipEntry(ze)
            For Each fil As Filter In _Filters
                fil.ApplyToZipEntry(ze)
            Next
        Next
    End Sub
    Private Sub _Zip_AddProgress(sender As Object, e As Ionic.Zip.AddProgressEventArgs)
        If e.EventType = Zip.ZipProgressEventType.Adding_AfterAddEntry Then
            RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Loading : " & e.EntriesTotal, Nothing))
            RemoveHandler _ZipFile.AddProgress, AddressOf _Zip_AddProgress
        End If
    End Sub
    Private Sub _BackMaker_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles _BackMaker.RunWorkerCompleted
        RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Loaded : " & _ZipFile.Count, Nothing))
        _BackMaker.Dispose()
        _BackMaker = Nothing
        SaveZip(ZipNodeTempFolder)
    End Sub

    'Background Saving :
    Private _ZipPath As String
    Private _ZipLocation As String
    Private _CancelSaving As Boolean = False
    Private _ZipSavingError As Exception
    Private _Resultfilesnodes As List(Of FileNode)
    'Save Zip :
    Private Sub SaveZip(ByVal location As String)
        ResetProgressFields()
        _ZipTotalSize = _ZipNode.Size
        _CancelSaving = False
        _ZipSavingError = Nothing
        _ZipFile.TempFileFolder = My.Computer.FileSystem.SpecialDirectories.Temp
        _ZipFile.ZipErrorAction = Zip.ZipErrorAction.Throw
        _ZipLocation = location
        _ZipPath = IO.Path.Combine(_ZipLocation, _ZipNode.Text & _ZipNode.Type.Extension)
        _BackSaver = New BackgroundWorker With {.WorkerSupportsCancellation = True}
        _BackSaver.RunWorkerAsync()
    End Sub
    'BackSaver :
    Private Sub _BackSaver_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles _BackSaver.DoWork
        If _BackSaver.CancellationPending = False Then
            _IsWorking = True
            Try
                If _ZipNode.Type = ZipType.SfxZip Then
                    Dim saveopts As Zip.SelfExtractorSaveOptions = _ZipNode.ZipSettings.SfxOptions.SfxSaveOptions
                    _ZipFile.SaveSelfExtractor(_ZipPath, saveopts)
                Else
                    _ZipFile.Save(_ZipPath)
                End If
            Catch ex As Exception
                _ZipSavingError = ex
                Me.CancelZip()
            End Try
        Else
            e.Cancel = True
        End If
    End Sub
    Private Sub _BackSaver_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles _BackSaver.RunWorkerCompleted
        If _CancelSaving = True Then
            If _ZipSavingError Is Nothing Then
                ResetProgressFields()
                _ZipTotalSize = _ZipNode.Size
                RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Canceled", Nothing))
            Else
                RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Error", _ZipSavingError))
            End If
        Else
            _Resultfilesnodes = New List(Of FileNode)
            Dim ext As String = _ZipNode.Type.Extension
            If _ZipNode.Type = ZipType.SfxZip OrElse _ZipNode.Type = ZipType.SimpleZip Then
                _Resultfilesnodes.Add(New FileNode(_ZipPath))
            Else
                Dim di As New IO.DirectoryInfo(_ZipLocation)
                For Each fi As IO.FileInfo In di.EnumerateFiles
                    If IO.Path.GetFileNameWithoutExtension(fi.FullName) = _ZipNode.Text AndAlso IO.Path.GetExtension(fi.FullName).StartsWith(".z") = True Then
                        _Resultfilesnodes.Add(New FileNode(fi.FullName))
                    End If
                Next
            End If
            RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Finished", _Resultfilesnodes))
        End If
        _BackSaver.Dispose()
        _BackSaver = Nothing
        _IsWorking = False
    End Sub
    'Saving Progress Event :
    Private _Update As Boolean = True
    Private _ZipEntry As String = ""
    Private Sub _Zip_SaveProgress(sender As Object, e As Ionic.Zip.SaveProgressEventArgs) Handles _ZipFile.SaveProgress
        If _CancelSaving = False Then
            Select Case e.EventType
                Case Zip.ZipProgressEventType.Saving_Started
                    RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged(String.Format("Saving : ({0:P02})", _ZipPercent), Nothing))
                Case Zip.ZipProgressEventType.Saving_BeforeWriteEntry
                    If IO.Path.GetExtension(e.CurrentEntry.FileName) = ".zip" Then
                        If _ZipEntry = e.CurrentEntry.FileName Then
                            _Update = False
                        Else
                            _Update = True
                        End If
                    Else
                        _Update = True
                    End If
                    _ZipEntry = e.CurrentEntry.FileName
                Case Zip.ZipProgressEventType.Saving_EntryBytesRead
                    If _Update = True Then
                        Me.UpdateBytes(e.BytesTransferred, e.TotalBytesToTransfer)
                        RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged(String.Format("Saving : ({0:P02})", _ZipPercent), Nothing))
                    End If
                Case Zip.ZipProgressEventType.Error_Saving
                    _ZipSavingError = New IO.IOException("In / Out Error Occured When Saving Zip File")
                    Me.CancelZip()
                    RaiseEvent InnerZipMakerStateChanged(Me, New InnerZipMakerStateChanged("Error", _ZipSavingError))
            End Select
        Else
            e.Cancel = True
            _BackSaver.CancelAsync()
        End If
    End Sub
    'Saving Zip Error :
    Private Sub _Zip_ZipError(sender As Object, e As Ionic.Zip.ZipErrorEventArgs) Handles _ZipFile.ZipError
        If e.EventType = Zip.ZipProgressEventType.Error_Saving Then
            _ZipSavingError = e.Exception
            Me.CancelZip()
        End If
    End Sub

    Public Event InnerZipMakerStateChanged As EventHandler(Of InnerZipMakerStateChanged)

End Class
